#include "SypEngine.h"

#ifdef CHECKMEM
#include "mmgr.h"
#endif
namespace Syp
{

	///////////////////////////////////////// JPG DEFINES ////////////////////////////////////
	struct my_error_mgr {
		struct jpeg_error_mgr pub;	/* "public" fields */

		jmp_buf setjmp_buffer;	/* for return to caller */
	};

	typedef struct my_error_mgr * my_error_ptr;

	METHODDEF(void)
		my_error_exit (j_common_ptr cinfo)
	{
		/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
		my_error_ptr myerr = (my_error_ptr) cinfo->err;

		/* Always display the message. */
		/* We could postpone this until after returning, if we chose. */
		(*cinfo->err->output_message) (cinfo);

		/* Return control to the setjmp point */
		longjmp(myerr->setjmp_buffer, 1);
	}

	///////////////////////////////////////// JPG DEFINES ////////////////////////////////////

	TextureObj::TextureObj(const String& filename):Resource(filename), width(32), height(32), internalFormat(TEXTURE2D)
	{
		Renderer::getRenderer().generateTexObj(this);
	}

	TextureObj::~TextureObj()
	{
		Renderer::getRenderer().removeTexture(this);
	}

	PInt TextureObj::getHeight()
	{
		return height;
	}

	PInt TextureObj::getWidth()
	{
		return width;
	}

	void TextureObj::dumpResourceInfo()
	{
		Resource::dumpResourceInfo();
		ErrorLogger::getLogger()<<"Texture Width X Height : "<<width<<" X "<<height<<std::endl;
	}

	void TextureObj::doLoad(void* arg)
	{

		unsigned char* data = 0;
		TexOptions* to = static_cast<TexOptions*>(arg);
		//Generate TextureObj
		TextureObj* tex = to->tex;

		FILE *fp = fopen(to->filename.c_str(), "rb");
		if (!fp)
		{
			ErrorLogger::getLogger()<<"Texture file does not exist : "<<to->filename<<std::endl;
			return;
		}
		String extension = to->filename.substr( to->filename.length()-3,3 );
		if(extension == "png" || extension == "PNG")
		{
			/////////////////////PNG START////////////////////////////////
			PByte header[8];
			fread(header, 1, 8, fp);
			bool is_png = !png_sig_cmp(header, 0, 8);
			if (!is_png)
			{
				ErrorLogger::getLogger()<<"Corrupted or invalid PNG file : "<<to->filename<<std::endl;
				return;
			}

			png_structp png_ptr = png_create_read_struct
				(PNG_LIBPNG_VER_STRING, 0,
				0, 0);
			if (!png_ptr)
				return;

			png_infop info_ptr = png_create_info_struct(png_ptr);
			if (!info_ptr)
			{
				png_destroy_read_struct(&png_ptr,
					(png_infopp)NULL, (png_infopp)NULL);
				return ;
			}

			png_infop end_info = png_create_info_struct(png_ptr);
			if (!end_info)
			{
				png_destroy_read_struct(&png_ptr, &info_ptr,
					(png_infopp)NULL);
				return ;
			}

			if (setjmp(png_jmpbuf(png_ptr)))
			{
				png_destroy_read_struct(&png_ptr, &info_ptr,
					&end_info);
				fclose(fp);
				return ;
			}

			png_init_io(png_ptr, fp);
			png_set_sig_bytes(png_ptr, 8);
			png_read_info(png_ptr, info_ptr);

			//	png_read_png(png_ptr, info_ptr,  PNG_TRANSFORM_PACKING, png_voidp_NULL);

			tex->width = png_get_image_width(png_ptr, info_ptr);
			tex->height = png_get_image_height(png_ptr, info_ptr);
			PInt BytesPPixel = 0;
			if(png_get_color_type(png_ptr,info_ptr) == PNG_COLOR_TYPE_RGB_ALPHA)
			{
				tex->format = RGBA;
				if(to->compressTextures)
				{
					tex->internalFormat = COMPRESSED_RGBA_DXT1;
					//	tex->internalFormat = RGBA;
				}
				else
				{
					tex->internalFormat = RGBA;
				}
				BytesPPixel = 4;

			}
			else
			{//No Alpha
				if(to->compressTextures)
				{
					tex->internalFormat = COMPRESSED_RGB_DXT1;
				}
				else
				{
					tex->internalFormat = RGB8;
				}
				tex->format = RGB;
				BytesPPixel = 3;
			}

			PInt rowBytes = png_get_rowbytes(png_ptr, info_ptr);

			data = new unsigned char[rowBytes * tex->height];


			for (PInt row = 0; row < tex->height; row++)
			{
				png_read_row( png_ptr, (unsigned char *)(data+((tex->height - row - 1)*rowBytes)), NULL );
			}

			png_destroy_read_struct(&png_ptr, &info_ptr,&end_info);

		}
		///////////////////////////////////PNG END//////////////////////////////////////
		else if(extension == "jpg" || extension == "JPG")
		{
			/* This struct contains the JPEG decompression parameters and pointers to
			* working space (which is allocated as needed by the JPEG library).
			*/
			struct jpeg_decompress_struct cinfo;
			/* We use our private extension JPEG error handler.
			* Note that this struct must live as long as the main JPEG parameter
			* struct, to avoid dangling-pointer problems.
			*/
			struct my_error_mgr jerr;
			/* More stuff */
			FILE * infile;		/* source file */
			//	JSAMPARRAY buffer;		/* Output row buffer */
			int row_stride;		/* physical row width in output buffer */

			/* In this example we want to open the input file before doing anything else,
			* so that the setjmp() error recovery below can assume the file is open.
			* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
			* requires it in order to read binary files.
			*/

			if ((infile = fopen(to->filename.c_str(), "rb")) == NULL)
			{
				ErrorLogger::getLogger()<<"Texture file does not exist : "<<to->filename<<std::endl;
				return;
			}

			/* Step 1: allocate and initialize JPEG decompression object */

			/* We set up the normal JPEG error routines, then override error_exit. */

			cinfo.err = jpeg_std_error(&jerr.pub);
			jerr.pub.error_exit = my_error_exit;
			/* Establish the setjmp return context for my_error_exit to use. */
			if (setjmp(jerr.setjmp_buffer)) {
				/* If we get here, the JPEG code has signaled an error.
				* We need to clean up the JPEG object, close the input file, and return.
				*/
				jpeg_destroy_decompress(&cinfo);
				fclose(infile);
				return ;
			}
			/* Now we can initialize the JPEG decompression object. */
			jpeg_create_decompress(&cinfo);

			/* Step 2: specify data source (eg, a file) */

			jpeg_stdio_src(&cinfo, infile);

			/* Step 3: read file parameters with jpeg_read_header() */

			(void) jpeg_read_header(&cinfo, TRUE);
			/* We can ignore the return value from jpeg_read_header since
			*   (a) suspension is not possible with the stdio data source, and
			*   (b) we passed TRUE to reject a tables-only JPEG file as an error.
			* See libjpeg.doc for more info.
			*/

			/* Step 4: set parameters for decompression */

			/* In this example, we don't need to change any of the defaults set by
			* jpeg_read_header(), so we do nothing here.
			*/

			/* Step 5: Start decompressor */

			(void) jpeg_start_decompress(&cinfo);
			/* We can ignore the return value since suspension is not possible
			* with the stdio data source.
			*/

			/* We may need to do some setup of our own at this point before reading
			* the data.  After jpeg_start_decompress() we have the correct scaled
			* output image dimensions available, as well as the output colormap
			* if we asked for color quantization.
			* In this example, we need to make an output work buffer of the right size.
			*/
			/* JSAMPLEs per row in output buffer */
			row_stride = cinfo.output_width * cinfo.output_components;
			tex->width = cinfo.output_width;
			tex->height = cinfo.output_height;

			///allocate data buffer
			data = new unsigned char[row_stride * tex->height];

			PByte **rowPtr = new PByte* [tex->height];

			for( unsigned i = 0; i < tex->height; i++ )
			{//Make the rowPtr array pointers point to our data buffer
				rowPtr[i] = &data[ (tex->height - i - 1) * row_stride ];
			}
			/* Make a one-row-high sample array that will go away when done with image */
			//buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

			/* Step 6: while (scan lines remain to be read) */
			/*           jpeg_read_scanlines(...); */

			/* Here we use the library's state variable cinfo.output_scanline as the
			* loop counter, so that we don't have to keep track ourselves.
			*/
			PInt rowsRead = 0;
			while (cinfo.output_scanline < cinfo.output_height) {
				/* jpeg_read_scanlines expects an array of pointers to scanlines.
				* Here the array is only one element long, but you could ask for
				* more than one scanline at a time if that's more convenient.
				*/
				rowsRead += jpeg_read_scanlines(&cinfo, &rowPtr[rowsRead], cinfo.output_height - rowsRead);
				/* Assume put_scanline_someplace wants a pointer and sample count. */
				//				put_scanline_someplace(buffer[0], row_stride);
			}



			/* At this point you may want to check to see whether any corrupt-data
			* warnings occurred (test whether jerr.pub.num_warnings is nonzero).
			*/

			/* And we're done! */


			tex->format = RGB;
			PInt BytesPPixel = 3;

			delete[] rowPtr;


			/* Step 7: Finish decompression */

			(void) jpeg_finish_decompress(&cinfo);
			/* We can ignore the return value since suspension is not possible
			* with the stdio data source.
			*/

			/* After finish_decompress, we can close the input file.
			* Here we postpone it until after no more JPEG errors are possible,
			* so as to simplify the setjmp error logic above.  (Actually, I don't
			* think that jpeg_destroy can do an error exit, but why assume anything...)
			*/
			fclose(infile);


			/* Step 8: Release JPEG decompression object */

			/* This is an important step since it will release a good deal of memory. */
			jpeg_destroy_decompress(&cinfo);


		}
		else if(extension == "dds" || extension == "DDS")
		{
			unsigned char *buffer;
			unsigned int buffer_length;
			size_t bytes_read;
			unsigned int tex_ID = 0;
			/*	error checks	*/
			fseek( fp, 0, SEEK_END );
			buffer_length = ftell( fp );
			fseek( fp, 0, SEEK_SET );
			buffer = (unsigned char *) malloc( buffer_length );
			if( NULL == buffer )
			{
				ErrorLogger::getLogger()<<"malloc failed for dds failed\n";
				fclose( fp );
				return;
			}
			bytes_read = fread( (void*)buffer, 1, buffer_length, fp );
			fclose( fp );
			if( bytes_read < buffer_length )
			{
				/*	huh?	*/
				buffer_length = static_cast<PInt>(bytes_read);
			}
			/*	now try to do the loading	*/
			loadDDSFromMemory((const unsigned char *const)buffer, buffer_length, 0, &data, tex);//no cubemaps for now
			free( buffer );
		}
		if(tex->internalFormat == TEXTURE2D)
		{
			if(to->compressTextures)
			{
				tex->internalFormat = COMPRESSED_RGB_DXT1;
			}
			else
			{
				tex->internalFormat = RGB8;
			}
		}



		fclose(fp);
		tex->hasMipMaps = to->buildMipMaps;
		Renderer::getRenderer().fillTexObj(tex,data);
		delete to;

	}

	TextureObj* TextureObj::loadDDSFromMemory(const unsigned char *const buffer,PInt buffer_length, int loading_as_cubemap, unsigned char** data, TextureObj* tex)
	{
		/*	variables	*/
		DDS_header header;
		unsigned int buffer_index = 0;
		unsigned int tex_ID = 0;
		/*	file reading variables	*/
		unsigned int S3TC_type = 0;
		unsigned char *DDS_data = 0;
		unsigned int DDS_main_size = 0;
		unsigned int DDS_full_size;
		unsigned int width, height;
		int mipmaps, cubemap, uncompressed, block_size = 16;
		unsigned int flag;
		unsigned int cf_target, ogl_target_start, ogl_target_end;
		unsigned int opengl_texture_type;
		int i;
		/*	1st off, does the filename even exist?	*/
		if( NULL == buffer )
		{
			/*	we can't do it!	*/
			ErrorLogger::showError("NULL buffer passed to loadDDSFromMemory");
			return 0;
		}
		if( buffer_length < sizeof( DDS_header ) )
		{
			/*	we can't do it!	*/
			ErrorLogger::showError("DDS file was too small to contain the DDS header");
			return 0;
		}
		/*	try reading in the header	*/
		memcpy ( (void*)(&header), (const void *)buffer, sizeof( DDS_header ) );
		buffer_index = sizeof( DDS_header );
		/*	validate the header (warning, "goto"'s ahead, shield your eyes!!)	*/
		flag = ('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24);
		if( header.dwMagic != flag ) {goto quick_exit;}
		if( header.dwSize != 124 ) {goto quick_exit;}
		/*	I need all of these	*/
		flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
		if( (header.dwFlags & flag) != flag ) {goto quick_exit;}
		/*	According to the MSDN spec, the dwFlags should contain
		DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if
		uncompressed.  Some DDS writers do not conform to the
		spec, so I need to make my reader more tolerant	*/
		/*	I need one of these	*/
		flag = DDPF_FOURCC | DDPF_RGB;
		if( (header.sPixelFormat.dwFlags & flag) == 0 ) {goto quick_exit;}
		if( header.sPixelFormat.dwSize != 32 ) {goto quick_exit;}
		if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) {goto quick_exit;}
		/*	make sure it is a type we can upload	*/
		if( (header.sPixelFormat.dwFlags & DDPF_FOURCC) &&
			!(
			(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) ||
			(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) ||
			(header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24)))
			) )
		{
			goto quick_exit;
		}
		/*	OK, validated the header, let's load the image data	*/
		width = header.dwWidth;
		height = header.dwHeight;
		tex->width = width;
		tex->height = height;
		uncompressed = 1 - (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC;
		cubemap = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP;
		if( uncompressed )
		{
			S3TC_type = Syp::RGB;
			tex->format = Syp::RGB;

			block_size = 3;
			if( header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS )
			{
				S3TC_type = Syp::RGBA;
				tex->format = Syp::RGBA;
				block_size = 4;
			}
			DDS_main_size = width * height * block_size;

		} else
		{
			/*	can we even handle direct uploading to OpenGL DXT compressed images?	*/
			if( !Renderer::getRenderer().getHardwareCpbRef().CompressedTextures)
			{
				/*	we can't do it!	*/
				ErrorLogger::showError("Direct upload of S3TC images not supported by the OpenGL driver or hardware.Try updating your drivers.");
				return 0;
			}
			/*	well, we know it is DXT1/3/5, because we checked above	*/
			switch( (header.sPixelFormat.dwFourCC >> 24) - '0' )
			{
			case 1:
				S3TC_type = COMPRESSED_RGBA_DXT1;
				tex->format = tex->internalFormat = Syp::COMPRESSED_RGBA_DXT1;
				block_size = 8;
				break;
			case 3:
				S3TC_type = COMPRESSED_RGBA_DXT3;
				tex->format = tex->internalFormat = Syp::COMPRESSED_RGBA_DXT3;
				block_size = 16;
				break;
			case 5:
				S3TC_type = COMPRESSED_RGBA_DXT5;
				tex->format = tex->internalFormat = Syp::COMPRESSED_RGBA_DXT5;
				block_size = 16;
				break;
			}
			DDS_main_size = ((width+3)>>2)*((height+3)>>2)*block_size;
		}
		if( cubemap )
		{
			/* does the user want a cubemap?	*/
			if( !loading_as_cubemap )
			{
				/*	we can't do it!	*/
				ErrorLogger::showError("DDS image was a cubemap\n");
				return 0;
			}
			/*	can we even handle cubemaps with the OpenGL driver?	*/
			if( !Renderer::getRenderer().getHardwareCpbRef().CubeMaps )
			{
				/*	we can't do it!	*/
				ErrorLogger::showError("Direct upload of cubemap images not supported by the OpenGL driver");
				return 0;
			}
			ogl_target_start = CUBE_MAP_POSITIVE_X;
			ogl_target_end =   CUBE_MAP_NEGATIVE_Z;
			opengl_texture_type = CUBE_MAP_TEXTURE;
		} else
		{
			/* does the user want a non-cubemap?	*/
			if( loading_as_cubemap )
			{
				/*	we can't do it!	*/
				ErrorLogger::showError("Could not load as CubeMap,DDS image was not a cubemap");
				return 0;
			}
			ogl_target_start = TEXTURE2D;
			ogl_target_end =   TEXTURE2D;
			opengl_texture_type = TEXTURE2D;
		}
		if( (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1) )
		{
			int shift_offset;
			mipmaps = header.dwMipMapCount - 1;
			DDS_full_size = DDS_main_size;
			if( uncompressed )
			{
				/*	uncompressed DDS, simple MIPmap size calculation	*/
				shift_offset = 0;
			} else
			{
				/*	compressed DDS, MIPmap size calculation is block based	*/
				shift_offset = 2;
			}
			for( i = 1; i <= mipmaps; ++ i )
			{
				int w, h;
				w = width >> (shift_offset + i);
				h = height >> (shift_offset + i);
				if( w < 1 )
				{
					w = 1;
				}
				if( h < 1 )
				{
					h = 1;
				}
				DDS_full_size += w*h*block_size;
			}
		} else
		{
			mipmaps = 0;
			DDS_full_size = DDS_main_size;
		}
		DDS_data =  new unsigned char[DDS_full_size];
		/*	got the image data RAM, create or use an existing OpenGL texture handle	*/

		/*	do this for each face of the cubemap!	*/
		for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target )
		{
			if( buffer_index + DDS_full_size <= buffer_length )
			{
				unsigned int byte_offset = DDS_main_size;
				memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size );
				buffer_index += DDS_full_size;
				/*	upload the main chunk	*/
				if( uncompressed )
				{
					/*	and remember, DXT uncompressed uses BGR(A),
					so swap to RGB(A) for ALL MIPmap levels	*/
					for( i = 0; i < static_cast<int>(DDS_full_size); i += block_size )
					{
						unsigned char temp = DDS_data[i];
						DDS_data[i] = DDS_data[i+2];
						DDS_data[i+2] = temp;
					}
				}

					/*	upload the mipmaps, if we have them	*/
					for( i = 1; i <= mipmaps; ++i )
					{
						int w, h, mip_size;
						w = width >> i;
						h = height >> i;
						if( w < 1 )
						{
							w = 1;
						}
						if( h < 1 )
						{
							h = 1;
						}
						/*	upload this mipmap	*/
						if( uncompressed )
						{
							mip_size = w*h*block_size;
						} else
						{
							mip_size = ((w+3)/4)*((h+3)/4)*block_size;
						}
						/*	and move to the next mipmap	*/
						byte_offset += mip_size;
					}
					/*	it worked!	*/
				} else
				{
					tex_ID = 0;
					cf_target = ogl_target_end + 1;
					ErrorLogger::showError("DDS file was too small for expected image data");
				}
			}/* end reading each face */

quick_exit:
			/*	report success or failure	*/
			(*data) = DDS_data;
			tex->dataSize = DDS_main_size;
			return tex;
		}

		TextureObj* TextureObj::loadTexture(const String& filename, bool buildMipMaps,bool compressTextures,bool async)
		{
			TextureObj* tex = static_cast<TextureObj*>(ResourceManager::getResourceManager().isResourceLoaded(filename));
			if(tex)
			{//Texture already exists, skip loading again
				return tex;
			}


			//Texture doesnt exist, start loading
			tex = new TextureObj(filename);
			TexOptions* to = new TexOptions();
			to->buildMipMaps = buildMipMaps;
			to->compressTextures = compressTextures;
			to->tex = tex;
			to->filename = filename;


			if(async)
			{
				//load in another thread
				SYPThread thread = SypEngine::createThread(TextureObj::doLoad,to);
				return tex;
			}
			doLoad(to);


			return tex;
		}

		TextureObj* TextureObj::loadFromMemory(Syp::TexOptions *to, Syp::PInt width, Syp::PInt height, unsigned char* data, Syp::TextureFormat format,  Syp::TextureFormat internalFormat,bool now)
		{
			TextureObj* tex = to->tex;
			tex = new TextureObj( to->filename );
			tex->internalFormat = internalFormat;
			tex->hasMipMaps = to->buildMipMaps;
			tex->format = format;
			tex->width = width;
			tex->height = height;

			Renderer::getRenderer().fillTexObj(tex,data, now);
			delete to;

			return tex;
		}
	}
